package de.codecrafters.tableview;

import de.codecrafters.tableview.colorizers.TableDataRowColorizer;
import de.codecrafters.tableview.listeners.OnScrollListener;
import de.codecrafters.tableview.listeners.TableDataClickListener;
import de.codecrafters.tableview.listeners.TableDataLongClickListener;
import de.codecrafters.tableview.listeners.TableHeaderClickListener;
import de.codecrafters.tableview.model.TableColumnModel;
import de.codecrafters.tableview.model.TableColumnWeightModel;
import de.codecrafters.tableview.providers.TableDataRowBackgroundProvider;
import de.codecrafters.tableview.swiperefreshlayout.SwiperefreshLayout;
import de.codecrafters.tableview.toolkit.TableDataRowBackgroundProviders;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import static de.codecrafters.tableview.AttrSetString.*;


/**
 * A view that is able to display data as a table. For bringing the data to the view the {@link TableDataAdapter} can be used.
 * For formatting the table headers the {@link TableHeaderAdapter} can be used.
 *
 * @author ISchwarz
 */
public class TableView<T> extends DirectionalLayout {

    private static final String LOG_TAG = TableView.class.getName();

    private static final int DEFAULT_COLUMN_COUNT = 4;
    private static final int DEFAULT_HEADER_ELEVATION = 1;
    private static final int DEFAULT_HEADER_COLOR = 0xFFCCCCCC;

    private final Set<TableDataLongClickListener<T>> dataLongClickListeners = new HashSet<>();
    private final Set<TableDataClickListener<T>> dataClickListeners = new HashSet<>();
    private final Set<OnScrollListener> onScrollListeners = new HashSet<>();
    private ComponentTransition layoutTransition;

    private TableDataRowBackgroundProvider<? super T> dataRowBackgroundProvider =
            TableDataRowBackgroundProviders.similarRowColor(0x00000000);
    private TableColumnModel columnModel;
    private TableHeaderView tableHeaderView;
    private ListContainer tableDataView;
    private TableDataAdapter<T> tableDataAdapter;
    private TableHeaderAdapter tableHeaderAdapter;

    private int headerElevation;
    private Color headerColor;
    private SwiperefreshLayout refreshLayout;


    /**
     * Creates a new TableView with the given context.\n
     *
     * @param context The context that shall be used.
     */
    public TableView(final Context context) {
        super(context);
        init(null);
    }


    /**
     * Creates a new TableView with the given context.\n
     *
     * @param context    The context that shall be used.
     * @param attributes The attributes that shall be set to the view.
     */
    public TableView(final Context context, final AttrSet attributes) {
        super(context, attributes);
        init(attributes);
    }


    /**
     * Creates a new TableView with the given context.
     *
     * @param context         The context that shall be used.
     * @param attributes      The attributes that shall be set to the view.
     * @param styleAttributes The style attributes that shall be set to the view.
     */
    public TableView(final Context context, final AttrSet attributes, final String styleAttributes) {
        super(context, attributes, styleAttributes);
        init(attributes);
    }

    private void init(final AttrSet attributes) {
        setOrientation(DirectionalLayout.VERTICAL);
        if (null == attributes) {
            headerColor = new Color(DEFAULT_HEADER_COLOR);
            headerElevation = DEFAULT_HEADER_ELEVATION;
            headerElevation = DEFAULT_HEADER_ELEVATION;
            columnModel = new TableColumnWeightModel(DEFAULT_COLUMN_COUNT);
        } else {
            setAttributes(attributes);
        }
        setupTableHeaderView();
        setupTableDataView();
        layoutTransition = new ComponentTransition();
    }

    /**
     * Replaces the default {@link TableHeaderView} with the given one.
     *
     * @param headerView The new {@link TableHeaderView} that should be set.
     */
    protected void setHeaderView(final TableHeaderView headerView) {
        this.tableHeaderView = headerView;

        tableHeaderView.setAdapter(tableHeaderAdapter);
        final ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(headerColor.getValue()));
        tableHeaderView.setBackground(shapeElement);

        if (getChildCount() == 2) {
            removeComponentAt(0);
        }

        addComponent(tableHeaderView, 0);
        setHeaderElevation(headerElevation);

        forceRefresh();
    }

    /**
     * Sets the {@link TableView} header visible or hides it.
     *
     * @param visible Whether the {@link TableView} header shall be visible or not.
     */
    public void setHeaderVisible(boolean visible) {
        setHeaderVisible(visible, 0);
    }

    /**
     * Sets the {@link TableView} header visible or hides it.
     *
     * @param animationDuration the animation duration
     * @param visible           Whether the {@link TableView} header shall be visible or not.
     */
    public void setHeaderVisible(boolean visible, int animationDuration) {
        if (visible && !isHeaderVisible()) {
            if (animationDuration > 0) {
                AnimatorProperty animatorValue = new AnimatorProperty();
                animatorValue.moveByY(0);
                animatorValue.setDuration(animationDuration);
                layoutTransition.setAnimatorProperty(ComponentTransition.SELF_SHOW, animatorValue);
                setComponentTransition(layoutTransition);
            } else {
                setComponentTransition(null);
            }
            addComponent(tableHeaderView, 0);
        } else if (!visible && isHeaderVisible()) {
            if (animationDuration > 0) {
                AnimatorProperty animatorValue = new AnimatorProperty();
                animatorValue.moveByY(-tableHeaderView.getHeight());
                animatorValue.setDuration(animationDuration);
                layoutTransition.setAnimatorProperty(ComponentTransition.SELF_SHOW, animatorValue);
                setComponentTransition(layoutTransition);
            } else {
                setComponentTransition(null);
            }
            removeComponent(tableHeaderView);
        }
    }

    /**
     * Gives a boolean that indicates if the {@link TableView} header is visible or not.
     *
     * @return A boolean that indicates if the {@link TableView} header is visible or not.
     */
    public boolean isHeaderVisible() {
        return getChildCount() == 2;
    }

    /**
     * Sets the view that shall be shown if no data is available.
     *
     * @param emptyDataView The reference to the view that shall be shown if no data is available.
     */
    public void setEmptyDataIndicatorView(final Component emptyDataView) {
    // tableDataView.setEmptyView(emptyDataView);
    }

    /**
     * Gives the view that is shown if no data is available.
     *
     * @return The view that is shown if no data is available.
     */
    public Component getEmptyDataIndicatorView() {

    // return tableDataView.getEmptyView();
        return null;
    }

    /**
     * stop refresh refreshLayout.
     */
    public void stopRefresh() {
        refreshLayout.stopRefreshAnimator();
    }

    /**
     * setOnPullRefreshListener.
     *
     * @param onPullRefreshListener 刷新监听
     */
    public void setOnPullRefreshListener(SwiperefreshLayout.OnPullRefreshListener onPullRefreshListener) {
        refreshLayout.setOnPullRefreshListener(onPullRefreshListener);
    }

    /**
     * Sets the given resource as background of the table header.
     *
     * @param resId The if of the resource tht shall be set as background of the table header.
     */
    public void setHeaderBackground(final int resId) {
        final ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(getContext().getColor(resId)));
        tableHeaderView.setBackground(shapeElement);
    }


    /**
     * Sets the elevation level of the header view. If you are not able to see the elevation shadow
     * you should set a background(-color) to the header.
     *
     * @param elevation The elevation that shall be set to the table header.
     */
    public void setHeaderElevation(final int elevation) {
    // ViewCompat.setElevation(tableHeaderView, elevation);
    }

    /**
     * Sets the given {@link TableDataRowColorizer} that will be used to define the background color for
     * every table data row.
     *
     * @param colorizer The {@link TableDataRowColorizer} that shall be used.
     * @deprecated This method is deprecated. Use {@link TableView#setDataRowBackgroundProvider} instead.
     */
    @Deprecated
    public void setDataRowColorizer(final TableDataRowColorizer<? super T> colorizer) {
        setDataRowBackgroundProvider(new TableDataRowBackgroundColorProvider<>(colorizer));
    }

    /**
     * Sets the given {@link TableDataRowBackgroundProvider} that will be used to define the background color for
     * every table data row.
     *
     * @param backgroundProvider The {@link TableDataRowBackgroundProvider} that shall be used.
     */
    public void setDataRowBackgroundProvider(final TableDataRowBackgroundProvider<? super T> backgroundProvider) {
        dataRowBackgroundProvider = backgroundProvider;
        tableDataAdapter.setRowBackgroundProvider(dataRowBackgroundProvider);
    }

    /**
     * Adds a {@link TableDataClickListener} to this table. This listener gets notified every time the user clicks
     * a certain data item.
     *
     * @param listener The listener that should be added as click listener.
     */
    public void addDataClickListener(final TableDataClickListener<T> listener) {
        dataClickListeners.add(listener);
    }

    /**
     * Adds a {@link TableDataLongClickListener} to this table. This listener gets notified every time the user clicks
     * long on a certain data item.
     *
     * @param listener The listener that should be added as long click listener.
     */
    public void addDataLongClickListener(final TableDataLongClickListener<T> listener) {
        dataLongClickListeners.add(listener);
    }

    /**
     * Adds a {@link OnScrollListener} to this table view.
     *
     * @param onScrollListener The {@link OnScrollListener} that shall be added.
     */
    public void addOnScrollListener(final OnScrollListener onScrollListener) {
        onScrollListeners.add(onScrollListener);
    }

    /**
     * Removes a {@link OnScrollListener} from this table view.
     *
     * @param onScrollListener The {@link OnScrollListener} that shall be removed.
     */
    public void removeOnScrollListener(final OnScrollListener onScrollListener) {
        onScrollListeners.remove(onScrollListener);
    }

    /**
     * Removes the given {@link TableDataClickListener} from the click listeners of this table.
     *
     * @param listener The listener that should be removed.
     * @deprecated This method has been deprecated in the version 2.2.0 for naming alignment reasons. Use the method
     * {@link TableView#removeDataClickListener(TableDataClickListener)} instead.
     */
    @Deprecated
    public void removeTableDataClickListener(final TableDataClickListener<T> listener) {
        dataClickListeners.remove(listener);
    }

    /**
     * Removes the given {@link TableDataClickListener} from the click listeners of this table.
     *
     * @param listener The listener that should be removed.
     */
    public void removeDataClickListener(final TableDataClickListener<T> listener) {
        dataClickListeners.remove(listener);
    }

    /**
     * Removes the given {@link TableDataLongClickListener} from the long click listeners of this table.
     *
     * @param listener The listener that should be removed.
     */
    public void removeDataLongClickListener(final TableDataLongClickListener<T> listener) {
        dataLongClickListeners.remove(listener);
    }

    /**
     * Adds the given {@link TableHeaderClickListener} to this table.
     *
     * @param listener The listener that shall be added to this table.
     */
    public void addHeaderClickListener(final TableHeaderClickListener listener) {
        tableHeaderView.addHeaderClickListener(listener);
    }

    /**
     * Removes the given {@link TableHeaderClickListener} from this table.
     *
     * @param listener The listener that shall be removed from this table.
     * @deprecated This method has been deprecated in the version 2.2.0 for naming alignment reasons. Use the method
     * {@link TableView#removeHeaderClickListener(TableHeaderClickListener)} instead.
     */
    @Deprecated
    public void removeHeaderListener(final TableHeaderClickListener listener) {
        tableHeaderView.removeHeaderClickListener(listener);
    }

    /**
     * Removes the given {@link TableHeaderClickListener} from this table.
     *
     * @param listener The listener that shall be removed from this table.
     */
    public void removeHeaderClickListener(final TableHeaderClickListener listener) {
        tableHeaderView.removeHeaderClickListener(listener);
    }

    /**
     * Gives the {@link TableHeaderAdapter} that is used to render the header views for each column.
     *
     * @return The {@link TableHeaderAdapter} that is currently set.
     */
    public TableHeaderAdapter getHeaderAdapter() {
        return tableHeaderAdapter;
    }

    /**
     * Sets the {@link TableHeaderAdapter} that is used to render the header views for each column.
     *
     * @param headerAdapter The {@link TableHeaderAdapter} that should be set.
     */
    public void setHeaderAdapter(final TableHeaderAdapter headerAdapter) {
        tableHeaderAdapter = headerAdapter;
        tableHeaderAdapter.setColumnModel(columnModel);
        tableHeaderView.setAdapter(tableHeaderAdapter);
        forceRefresh();
    }

    /**
     * Gives the {@link TableDataAdapter} that is used to render the data view for each cell.
     *
     * @return The {@link TableDataAdapter} that is currently set.
     */
    public TableDataAdapter<T> getDataAdapter() {
        return tableDataAdapter;
    }

    /**
     * Sets the {@link TableDataAdapter} that is used to render the data view for each cell.
     *
     * @param dataAdapter The {@link TableDataAdapter} that should be set.
     */
    public void setDataAdapter(final TableDataAdapter<T> dataAdapter) {
        tableDataAdapter = dataAdapter;
        tableDataAdapter.setColumnModel(columnModel);
        tableDataAdapter.setRowBackgroundProvider(dataRowBackgroundProvider);
        tableDataView.setItemProvider(tableDataAdapter);
        forceRefresh();
    }

    /**
     * Gives the {@link TableColumnModel} which is currently set to this {@link TableView}.
     *
     * @return The current {@link TableColumnModel}
     */
    public TableColumnModel getColumnModel() {
        return columnModel;
    }

    /**
     * Sets the given {@link TableColumnModel} to this {@link TableView}.
     *
     * @param columnModel The {@link TableColumnModel} that shall be used.
     */
    public void setColumnModel(final TableColumnModel columnModel) {
        this.columnModel = columnModel;
        this.tableHeaderAdapter.setColumnModel(this.columnModel);
        this.tableDataAdapter.setColumnModel(this.columnModel);
        forceRefresh();
    }

    /**
     * Gives the number of columns of this table.
     *
     * @return The current number of columns.
     */
    public int getColumnCount() {
        return columnModel.getColumnCount();
    }

    /**
     * Sets the number of columns of this table.
     *
     * @param columnCount The number of columns.
     */
    public void setColumnCount(final int columnCount) {
        columnModel.setColumnCount(columnCount);
        forceRefresh();
    }

    /**
     * Sets the column weight (the relative width of the column) of the given column.
     *
     * @param columnIndex  The index of the column the weight should be set to.
     * @param columnWeight The weight that should be set to the column.
     * @deprecated This method has been deprecated in the version 2.4.0. Use the method
     * {@link #setColumnModel(TableColumnModel)} instead.
     */
    @Deprecated
    public void setColumnWeight(final int columnIndex, final int columnWeight) {
        if (columnModel instanceof TableColumnWeightModel) {
            TableColumnWeightModel columnWeightModel = (TableColumnWeightModel) columnModel;
            columnWeightModel.setColumnWeight(columnIndex, columnWeight);
            forceRefresh();
        }
    }

    /**
     * Gives the column weight (the relative width of the column) of the given column.
     *
     * @param columnIndex The index of the column the weight should be returned.
     * @return The weight of the given column index.
     * @deprecated This method has been deprecated in the version 2.4.0. Use the method
     * {@link #getColumnModel()} instead.
     */
    @Deprecated
    public int getColumnWeight(final int columnIndex) {
        if (columnModel instanceof TableColumnWeightModel) {
            TableColumnWeightModel columnWeightModel = (TableColumnWeightModel) columnModel;
            return columnWeightModel.getColumnWeight(columnIndex);
        }
        return -1;
    }


    private void forceRefresh() {
        if (tableHeaderView != null) {
            tableHeaderView.invalidate();
            tableHeaderAdapter.notifyDataChanged();
        }
        if (tableDataView != null) {
            refreshLayout.stopRefreshAnimator();
            tableDataView.invalidate();
            tableDataAdapter.notifyDataChanged();
        }
    }

    private void setAttributes(final AttrSet attributes) {
        headerColor = attributes.getAttr(TABLEVIEW_HEADERCOLOR).isPresent() ? attributes.getAttr(TABLEVIEW_HEADERCOLOR).get().getColorValue() : (new Color(DEFAULT_HEADER_COLOR));
        headerElevation = attributes.getAttr(TABLEVIEW_HEADERELEVATION).isPresent() ? attributes.getAttr(TABLEVIEW_HEADERELEVATION).get().getIntegerValue() : DEFAULT_HEADER_ELEVATION;
        int columnCount = attributes.getAttr(TABLEVIEW_COLUMNCOUNT).isPresent() ? attributes.getAttr(TABLEVIEW_COLUMNCOUNT).get().getIntegerValue() : DEFAULT_COLUMN_COUNT;
        columnModel = new TableColumnWeightModel(columnCount);
    }


    private void setupTableHeaderView() {
        if (isInEditMode()) {
            tableHeaderAdapter = new EditModeTableHeaderAdapter(getContext());
        } else {
            tableHeaderAdapter = new DefaultTableHeaderAdapter(getContext());
        }

        final TableHeaderView tableHeaderView = new TableHeaderView(getContext());
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(new RgbColor(0xFFCCCCCC));
        tableHeaderView.setBackground(shapeElement);
        setHeaderView(tableHeaderView);
    }

    private boolean isInEditMode() {
        return false;
    }

    private void setupTableDataView() {
        if (isInEditMode()) {
            tableDataAdapter = new EditModeTableDataAdapter(getContext());
        } else {
            tableDataAdapter = new DefaultTableDataAdapter(getContext());
        }

        tableDataAdapter.setRowBackgroundProvider(dataRowBackgroundProvider);

        tableDataView = new ListContainer(getContext());
        tableDataView.setItemClickedListener(new InternalDataClickListener());
        tableDataView.setItemLongClickedListener(new InternalDataLongClickListener());
        tableDataView.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
        tableDataView.setItemProvider(tableDataAdapter);
        tableDataView.setScrolledListener(new InternalOnScrollListener());


        refreshLayout = new SwiperefreshLayout(getContext());
        refreshLayout.addContentView(tableDataView);
        addComponent(refreshLayout);
    }

    /**
     * Internal management of clicks on the data view.
     *
     * @author ISchwarz
     */
    private class InternalDataClickListener implements ListContainer.ItemClickedListener {


        private void informAllListeners(final int rowIndex) {
            final T clickedObject = (T) tableDataAdapter.getItem(rowIndex);

            for (final TableDataClickListener<T> listener : dataClickListeners) {
                listener.onDataClicked(rowIndex, clickedObject);
                // continue calling listeners
            }
        }

        @Override
        public void onItemClicked(ListContainer listContainer, Component component, int i, long l) {
            informAllListeners(i);
        }
    }

    /**
     * Internal long click management of clicks on the data view.
     *
     * @author ISchwarz
     */
    private class InternalDataLongClickListener implements ListContainer.ItemLongClickedListener {


        private boolean informAllListeners(final int rowIndex) {
            final T clickedObject = (T) tableDataAdapter.getItem(rowIndex);
            boolean isConsumed = false;

            for (final TableDataLongClickListener<T> listener : dataLongClickListeners) {
                isConsumed |= listener.onDataLongClicked(rowIndex, clickedObject);
            }
            return isConsumed;
        }

        @Override
        public boolean onItemLongClicked(ListContainer listContainer, Component component, int i, long l) {
            return informAllListeners(i);
        }
    }

    /**
     * {@link OnScrollListener}s.
     *
     * @author ISchwarz
     */
    private class InternalOnScrollListener implements Component.ScrolledListener {


        @Override
        public void onContentScrolled(Component component, int i, int i1, int i2, int i3) {
            for (final OnScrollListener onScrollListener : onScrollListeners) {
                onScrollListener.onScroll(tableDataView, tableDataView.getFirstVisibleItemPosition(), tableDataView.getVisibleIndexCount(), tableDataView.getChildCount());
            }
        }

        @Override
        public void scrolledStageUpdate(Component component, int newStage) {
            final OnScrollListener.ScrollState scrollState = OnScrollListener.ScrollState.fromValue(newStage);

            for (final OnScrollListener onScrollListener : onScrollListeners) {
                onScrollListener.onScrollStateChanged(tableDataView, scrollState);
            }
        }

    }

    /**
     * The {@link TableHeaderAdapter} that is used by default. It contains the column model of the
     * table but no headers.
     *
     * @author ISchwarz
     */
    private class DefaultTableHeaderAdapter extends TableHeaderAdapter {

        public DefaultTableHeaderAdapter(final Context context) {
            super(context, columnModel);
        }

        @Override
        public Component getHeaderView(int columnIndex, ComponentContainer parentView) {
            final Text view = new Text(getContext());
            view.setText(" ");
            view.setPadding(20, 40, 20, 40);
            return view;
        }


        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }
    }

    /**
     * The {@link TableDataAdapter} that is used by default. It contains the column model of the
     * table but no data.
     *
     * @author ISchwarz
     */
    private class DefaultTableDataAdapter extends TableDataAdapter<T> {

        public DefaultTableDataAdapter(final Context context) {
            super(context, columnModel, new ArrayList<T>());
        }

        @Override
        public Component getCellView(final int rowIndex, final int columnIndex, final ComponentContainer parentView) {
            return new Text(getContext());
        }

        @Override
        public int getCount() {
            return 50;
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return i;
        }
    }

    /**
     * The {@link TableHeaderAdapter} that is used while the view is in edit mode.
     *
     * @author ISchwarz
     */
    private class EditModeTableHeaderAdapter extends TableHeaderAdapter {

        private static final int TEXT_SIZE = 17;

        public EditModeTableHeaderAdapter(final Context context) {
            super(context, columnModel);
        }

        @Override
        public Component getHeaderView(final int columnIndex, final ComponentContainer parentView) throws NotExistException, WrongTypeException, IOException {
            final Text textView = new Text(getContext());
            textView.setText(getContext().getResourceManager().getElement(ResourceTable.String_default_header).getString(columnIndex));
            textView.setPadding(20, 40, 20, 40);
            textView.setTextSize(TEXT_SIZE, Text.TextSizeType.VP);
            return textView;
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return i;
        }
    }

    /**
     * The {@link TableDataAdapter} that is used while the view is in edit mode.
     *
     * @author ISchwarz
     */
    private class EditModeTableDataAdapter extends TableDataAdapter<T> {

        private static final int TEXT_SIZE = 16;

        public EditModeTableDataAdapter(final Context context) {
            super(context, columnModel, new ArrayList<T>());
        }


        @Override
        public int getCount() {
            return 50;
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return i;
        }


        @Override
        public Component getCellView(int rowIndex, int columnIndex, ComponentContainer parentView) throws NotExistException, WrongTypeException, IOException {
            final Text textView = new Text(getContext());
            textView.setText(getResources().getElement(ResourceTable.String_default_cell).getString(columnIndex, rowIndex));
            textView.setPadding(20, 10, 20, 10);
            textView.setTextSize(TEXT_SIZE, Text.TextSizeType.VP);
            return textView;
        }
    }

}
